/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.formserver.viewmodel.common;

import com.inspur.edp.bef.bizentity.GspBizEntityObject;
import com.inspur.edp.bef.bizentity.GspBusinessEntity;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.collection.GspAssociationCollection;
import com.inspur.edp.cef.designtime.api.collection.GspFieldCollection;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspAssociationKey;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import com.inspur.edp.das.commonmodel.collection.GspObjectCollection;
import com.inspur.edp.das.commonmodel.entity.GspCommonElement;
import com.inspur.edp.formserver.viewmodel.GspViewModel;
import com.inspur.edp.formserver.viewmodel.GspViewModelElement;
import com.inspur.edp.formserver.viewmodel.GspViewObject;
import com.inspur.edp.formserver.viewmodel.collection.VMElementCollection;
import com.inspur.edp.formserver.viewmodel.collection.ViewObjectCollection;
import com.inspur.edp.formserver.viewmodel.common.mapping.GspVoElementMapping;
import com.inspur.edp.formserver.viewmodel.common.mapping.GspVoElementSourceType;
import com.inspur.edp.formserver.viewmodel.common.mapping.GspVoObjectSourceType;
import com.inspur.edp.formserver.viewmodel.exception.ViewModelException;
import com.inspur.edp.formserver.viewmodel.exception.VoModelErrorCodes;
import io.iec.edp.caf.commons.utils.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

//be上新增加的对象，字段等，同步到vo上，IDP用
public class ConvertVoUtils {

    public static GspViewModel dealViewModel(GspBusinessEntity be, GspViewModel viewModel) {

        GspBizEntityObject bizObject = be.getMainObject();
        GspViewObject viewObject = viewModel.getMainObject();
        dealObject(be, bizObject, viewObject, null);
        dealActions(be, viewModel);
        return viewModel;
    }

    public static void dealBeRefElements(GspBusinessEntity be, GspViewModel model) {
        LinkBeUtils utils = new LinkBeUtils(true);
        utils.linkBeRefElements(model);

    }

    public static void dealActions(GspBusinessEntity be, GspViewModel model) {
        LinkBeUtils utils = new LinkBeUtils(true);
        utils.linkActions(model);
    }

    /**
     * VO同步对象
     * @param be 业务实体
     * @param bizObject 业务实体节点
     * @param viewObject 视图对象节点
     * @param parentVoIDElementId 父节点的唯一标识字段的唯一标识
     */
    private static void dealObject(GspBusinessEntity be, GspBizEntityObject bizObject, GspViewObject viewObject, String parentVoIDElementId) {
        dealObjectBasicInfo(bizObject, viewObject);

        dealElements(bizObject, viewObject);

        dealObjectSelfInfo(bizObject, viewObject, parentVoIDElementId);
        //联动中处理
        dealAddChildObjects(be, bizObject, viewObject);

    }

    private static void dealAddChildObjects(GspBusinessEntity be, GspBizEntityObject bizObject, GspViewObject viewObject) {
        if (bizObject == null || CollectionUtils.isEmpty(bizObject.getContainChildObjects()))
            return;
        GspObjectCollection beObjectCollection = bizObject.getContainChildObjects();
        for (IGspCommonObject beObj : beObjectCollection) {
            // VO中查询对应子对象
            GspViewObject gspViewObject = getExistBizChildObject(viewObject, beObj.getID());
            // 若存在对应子对象, 则递归同步
            if (gspViewObject != null) {
                dealObject(be, (GspBizEntityObject) beObj, gspViewObject, viewObject.getIDElement().getID());
                continue;
            }
            // 子对象不存在则新增
            IGspCommonObject newViewObject = ConvertUtils.toObject(beObj, viewObject.getMapping().getTargetMetadataPkgName(), viewObject.getMapping().getTargetMetadataId(), viewObject.getIDElement().getID(), GspVoObjectSourceType.BeObject);
            viewObject.getContainChildObjects().add(newViewObject);
        }

    }

    /**
     * 处理对象的基本信息
     * @param bizObject 业务实体节点
     * @param viewObject 视图对象节点
     * @param parentVoIDElementId 视图对象父节点的唯一标识字段的唯一标识
     */
    private static void dealObjectSelfInfo(GspBizEntityObject bizObject, GspViewObject viewObject, String parentVoIDElementId) {
        // 获取视图对象所有字段, 并做成其对应be字段id与当前字段id的映射关系
        Map<String, String> collect = viewObject.getAllElementList(false).stream()
                .filter(item -> item != null && ((GspViewModelElement) item).getMapping() != null)
                .collect(Collectors.toMap(item -> ((GspViewModelElement) item).getMapping().getTargetElementId(), IGspCommonField::getID));
        // 根据BE唯一标识字段的id, 获取视图对象的唯一标识字段,并记录其id
        ConvertUtils.updateColumnGenerateId(bizObject, viewObject, collect);

        // 更新视图对象节点间关联关系
        viewObject.getKeys().clear();
        for (GspAssociationKey beKey : bizObject.getKeys()) {
            // 获取be节点关联关系
            GspAssociationKey key = beKey.clone();
            // 获取be当前节点关联字段在vo中映射字段的id, 设置为sourceElement
            key.setSourceElement(getVmElementByMappedBizEleId(viewObject.getAllElementList(true), key.getSourceElement()).getID());
            // 获取视图对象父节点的唯一标识字段的id, 设置给targetElement
            key.setTargetElement(parentVoIDElementId);
            viewObject.getKeys().add(key);
        }
    }

    /**
     * 根据BE字段ID获取视图对象中的字段
     * @param vmElements 视图对象字段列表
     * @param bizEleId be字段id
     * @return 视图对象对应字段
     */
    private static GspCommonElement getVmElementByMappedBizEleId(ArrayList<IGspCommonElement> vmElements, String bizEleId) {
        if (vmElements.isEmpty()) {
            throw new ViewModelException(VoModelErrorCodes.GSP_VIEWOBJECT_MODEL_1006, null, bizEleId);
        }
        for (IGspCommonElement item : vmElements) {
            if (bizEleId.equals(((GspViewModelElement) item).getMapping().getTargetObjId())) {
                return (GspCommonElement) item;
            }
        }
        throw new ViewModelException(VoModelErrorCodes.GSP_VIEWOBJECT_MODEL_1006, null, bizEleId);
    }

    private static void dealElements(GspBizEntityObject bizObject, GspViewObject viewObject) {
        // 获取BE字段列表
        ArrayList<IGspCommonField> bizElemens = bizObject.getContainElements().getAllItems(item -> !item.getIsVirtual());

        // 获取BE字段与ID映射Map
        Map<String, IGspCommonField> bizElementMap
                = bizElemens.stream().collect(Collectors.toMap(IGspCommonField::getID, item -> item));

        // 遍历VO字段列表
        VMElementCollection voElementList = viewObject.getContainElements();
        for (int i = voElementList.size() - 1; i >= 0; i--) {
            GspViewModelElement voElement = (GspViewModelElement) voElementList.get(i);
            // 检查BE是否存在VO对应字段
            String beIdInView = voElement.getMapping().getTargetElementId();
            // 检查VO字段是否存在映射的BE字段
            if (bizElementMap.containsKey(beIdInView)) {
                // 标记BE字段已存在映射
                IGspCommonField beElement = bizElementMap.remove(beIdInView);
                // 基于BE字段重新生成VO字段
                GspViewModelElement newViewEle = ConvertUtils.toElement((IGspCommonElement) beElement, viewObject.getMapping().getTargetMetadataPkgName(), viewObject.getMapping().getTargetMetadataId(), GspVoElementSourceType.BeElement);
                // 将原VO字段相关ID 赋值给新构造VO字段
                syncVoElementIds(voElement, newViewEle);
                // 更新VO字段为新构造的VO字段
                voElementList.set(i, newViewEle);

            } else {
                // 若对应的BE字段已不存在, 则移除VO中此字段
                voElementList.remove(i);
            }
        }

        // 剩余的BE字段识别为新增字段, 构造对应的VO字段并补充到VO中
        for (IGspCommonField beEle : bizElementMap.values()) {
            IGspCommonElement newViewEle = ConvertUtils.toElement((IGspCommonElement) beEle, viewObject.getMapping().getTargetMetadataPkgName(), viewObject.getMapping().getTargetMetadataId(), GspVoElementSourceType.BeElement);
            newViewEle.setBelongObject(viewObject);
            viewObject.getContainElements().add(newViewEle);
        }

    }

    /**
     * 将旧VO字段中相关ID赋予新构造的VO字段
     * @param voElement 旧VO字段
     * @param newViewEle 新VO字段
     */
    private static void syncVoElementIds(GspViewModelElement voElement, GspViewModelElement newViewEle) {
        // VO字段ID重赋
        newViewEle.setID(voElement.getID());
        // 将旧VO字段关联信息中的ID等字段赋值给新构造的VO字段
        syncVoElementChildAssoIds(voElement.getChildAssociations(), newViewEle.getChildAssociations(), newViewEle.getID());
    }

    /**
     * 将旧VO字段中关联信息中的ID等字段赋予新构造的VO字段
     *
     * @param sourceAssos 旧VO字段关联信息
     * @param targetAssos 新VO字段关联信息
     * @param voElementId 当前VO字段ID
     */
    private static void syncVoElementChildAssoIds(GspAssociationCollection sourceAssos, GspAssociationCollection targetAssos, String voElementId) {
        // 遍历旧VO关联信息, 根据关联信息的 refModelID 和 refObjectID 构建关联信息Map
        Map<String, GspAssociation> sourceAssoMap = sourceAssos.stream()
                .collect(Collectors.toMap(item -> item.getRefModelID() + "_" + item.getRefObjectID(), item -> item));

        // 遍历新VO关联信息, 若存在对应旧VO关联信息, 则赋予旧VO关联信息中的ID等字段
        for (GspAssociation targetAsso : targetAssos) {
            String key = targetAsso.getRefModelID() + "_" + targetAsso.getRefObjectID();
            if (sourceAssoMap.containsKey(key)) {
                GspAssociation sourceAsso = sourceAssoMap.get(key);
                // 同步关联信息中的 ID
                targetAsso.setId(sourceAsso.getId());
                // 同步关联信息中的 refElementCollection
                syncVoElementChildAssoRefElementIds(sourceAsso.getRefElementCollection(), targetAsso.getRefElementCollection());
            }

            // 同步关联信息中的 的关联字段ID
            for (GspAssociationKey gspAssociationKey : targetAsso.getKeyCollection()) {
                gspAssociationKey.setTargetElement(voElementId);
            }
        }

    }

    /**
     * 同步新旧VO关联信息中的 refElementCollection
     * @param sourceRefElementCollection 旧VO关联信息中的 refElementCollection
     * @param targetRefElementCollection 新VO关联信息中的 refElementCollection
     */
    private static void syncVoElementChildAssoRefElementIds(GspFieldCollection sourceRefElementCollection,
                                                            GspFieldCollection targetRefElementCollection) {
        // 遍历旧VO关联信息中的 refElementCollection, 构建 refElementId Map
        Map<String, String> sourceRefElementIdMap = new HashMap<>();
        for (IGspCommonField sourceRefEle : sourceRefElementCollection) {
            GspViewModelElement voElement = (GspViewModelElement)sourceRefEle;
            GspVoElementMapping mapping = voElement.getMapping();
            sourceRefElementIdMap.put(mapping.getTargetMetadataId() + "_" + mapping.getTargetObjectId() + "_" + mapping.getTargetElementId(), voElement.getID());
        }

        // 遍历新VO关联信息中的 refElementCollection, 若存在对应旧VO关联信息, 则赋予旧VO关联信息中的ID
        for (IGspCommonField targetRefEle : targetRefElementCollection) {
            GspViewModelElement voElement = (GspViewModelElement)targetRefEle;
            GspVoElementMapping mapping = voElement.getMapping();
            String key = mapping.getTargetMetadataId() + "_" + mapping.getTargetObjectId() + "_" + mapping.getTargetElementId();
            if (sourceRefElementIdMap.containsKey(key)) {
                targetRefEle.setID(sourceRefElementIdMap.get(key));
            }
        }
    }

    private static GspViewObject getExistBizChildObject(GspViewObject bizObject, String objectId) {
        ViewObjectCollection viewObjectCollection = bizObject.getContainChildObjects();
        for (IGspCommonObject ele : viewObjectCollection) {
            String objectIdInView = ((GspViewObject) ele).getMapping().getTargetObjId();
            if (objectId.equals(objectIdInView)) {
                return (GspViewObject) ele;
            }
        }
        return null;
    }

    private static void dealObjectBasicInfo(GspBizEntityObject bizObject, GspViewObject viewObject) {
    }
}
